# Copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import os
import yaml
import glob
import argparse

# ignore warning log
import warnings
warnings.filterwarnings('ignore')

import paddle
from ppdet.core.workspace import load_config, merge_config
from ppdet.engine import Trainer, TrainerCot, init_parallel_env, set_random_seed, init_fleet_env
from ppdet.engine.trainer_ssod import Trainer_DenseTeacher, Trainer_ARSL, Trainer_Semi_RTDETR
from ppdet.slim import build_slim_model
from ppdet.utils.cli import ArgsParser, merge_args
import ppdet.utils.check as check
from ppdet.utils.logger import setup_logger
logger = setup_logger('train')

##==============================================================================
def check_utils(cfg, FLAGS):
    # disable npu in config by default
    if 'use_npu' not in cfg:
        cfg.use_npu = False

    # disable xpu in config by default
    if 'use_xpu' not in cfg:
        cfg.use_xpu = False

    if 'use_gpu' not in cfg:
        cfg.use_gpu = False

    # disable mlu in config by default
    if 'use_mlu' not in cfg:
        cfg.use_mlu = False

    if cfg.use_gpu:
        paddle.set_device('gpu')
    elif cfg.use_npu:
        paddle.set_device('npu')
    elif cfg.use_xpu:
        paddle.set_device('xpu')
    elif cfg.use_mlu:
        paddle.set_device('mlu')
    else:
        paddle.set_device('cpu')

    if FLAGS.slim_config:
        cfg = build_slim_model(cfg, FLAGS.slim_config)

    check.check_config(cfg)
    check.check_gpu(cfg.use_gpu)
    check.check_npu(cfg.use_npu)
    check.check_xpu(cfg.use_xpu)
    check.check_mlu(cfg.use_mlu)
    check.check_version()

##==============================================================================
def get_test_images(infer_dir, infer_img):
    """
    Get image path list in TEST mode
    """
    assert infer_img is not None or infer_dir is not None, \
        "--infer_img or --infer_dir should be set"
    assert infer_img is None or os.path.isfile(infer_img), \
            "{} is not a file".format(infer_img)
    assert infer_dir is None or os.path.isdir(infer_dir), \
            "{} is not a directory".format(infer_dir)

    # infer_img has a higher priority
    if infer_img and os.path.isfile(infer_img):
        return [infer_img]

    images = set()
    infer_dir = os.path.abspath(infer_dir)
    assert os.path.isdir(infer_dir), \
        "infer_dir {} is not a directory".format(infer_dir)
    exts = ['jpg', 'jpeg', 'png', 'bmp']
    exts += [ext.upper() for ext in exts]
    for ext in exts:
        images.update(glob.glob('{}/*.{}'.format(infer_dir, ext)))
    images = list(images)

    assert len(images) > 0, "no image found in {}".format(infer_dir)
    logger.info("Found {} inference images in total.".format(len(images)))

    return images

##==============================================================================
def train_process(cfg, FLAGS):
    print('train_process FLAGS = ', type(FLAGS), FLAGS)
    print('train_process cfg = ', type(cfg), cfg)

    # init fleet environment
    if cfg.fleet:
        init_fleet_env(cfg.get('find_unused_parameters', False))
    else:
        # init parallel environment if nranks > 1
        init_parallel_env()

    if FLAGS.enable_ce:
        set_random_seed(0)

    # build trainer
    ssod_method = cfg.get('ssod_method', None)
    if ssod_method is not None:
        if ssod_method == 'DenseTeacher':
            trainer = Trainer_DenseTeacher(cfg, mode='train')
        elif ssod_method == 'ARSL':
            trainer = Trainer_ARSL(cfg, mode='train')
        elif ssod_method == 'Semi_RTDETR':
            trainer = Trainer_Semi_RTDETR(cfg, mode='train')
        else:
            raise ValueError(
                "Semi-Supervised Object Detection only no support this method.")
    elif cfg.get('use_cot', False):
        trainer = TrainerCot(cfg, mode='train')
    else:
        trainer = Trainer(cfg, mode='train')

    # load weights
    if FLAGS.resume is not None:
        trainer.resume_weights(FLAGS.resume)
    elif 'pretrain_student_weights' in cfg and 'pretrain_teacher_weights' in cfg \
            and cfg.pretrain_teacher_weights and cfg.pretrain_student_weights:
        trainer.load_semi_weights(cfg.pretrain_teacher_weights,
                                  cfg.pretrain_student_weights)
    elif 'pretrain_weights' in cfg and cfg.pretrain_weights:
        trainer.load_weights(cfg.pretrain_weights)

    # training
    trainer.train(FLAGS.eval)

def train( base_cfg_path, user_cfg_dict, output_dir ):
    # 模拟输入参数
    FLAGS = argparse.Namespace(amp=False, enable_ce=False, eval=True, fleet=False, opt={}, profiler_options=None, proposals_path='sniper/proposals.json', resume=None, save_prediction_only=False, save_proposals=False, slim_config=None, to_static=False, use_vdl=False, use_wandb=False, vdl_log_dir='vdl_log_dir/scalar', save_dir=output_dir)

    # 加载基础配置和数据配置
    cfg = load_config(base_cfg_path)
    merge_args(cfg, FLAGS)
    merge_config(user_cfg_dict)

    # 检查硬件设备情况
    check_utils(cfg, FLAGS)

    # 开始启动正式训练
    train_process(cfg, FLAGS)

##==============================================================================
def infer_process(cfg, FLAGS):
    print('infer_process FLAGS = ', type(FLAGS), FLAGS)
    print('infer_process cfg = ', type(cfg), cfg)

    ssod_method = cfg.get('ssod_method', None)
    if ssod_method == 'ARSL':
        trainer = Trainer_ARSL(cfg, mode='test')
        trainer.load_weights(cfg.weights, ARSL_eval=True)
    else:
        trainer = Trainer(cfg, mode='test')
        trainer.load_weights(cfg.weights)
    # get inference images
    images = get_test_images(FLAGS.infer_dir, FLAGS.infer_img)

    # inference
    if FLAGS.slice_infer:
        trainer.slice_predict(
            images,
            slice_size=FLAGS.slice_size,
            overlap_ratio=FLAGS.overlap_ratio,
            combine_method=FLAGS.combine_method,
            match_threshold=FLAGS.match_threshold,
            match_metric=FLAGS.match_metric,
            draw_threshold=FLAGS.draw_threshold,
            output_dir=FLAGS.output_dir,
            save_results=FLAGS.save_results,
            visualize=FLAGS.visualize)
    else:
        trainer.predict(
            images,
            draw_threshold=FLAGS.draw_threshold,
            output_dir=FLAGS.output_dir,
            save_results=FLAGS.save_results,
            visualize=FLAGS.visualize)

def infer( base_cfg_path, user_cfg_dict, checkpoint_path, intput_dir, output_dir ):
    FLAGS = argparse.Namespace(combine_method='nms', draw_threshold=0.5, infer_dir=intput_dir, infer_img=None, match_metric='ios', match_threshold=0.6, opt={}, output_dir=output_dir, overlap_ratio=[0.25, 0.25], save_results=False, slice_infer=False, slice_size=[640, 640], slim_config=None, use_vdl=False, vdl_log_dir='vdl_log_dir/image', visualize=True)
    
    # 加载各种配置
    cfg = load_config(base_cfg_path)
    merge_args(cfg, FLAGS)
    merge_config(user_cfg_dict)
    merge_config({ "weights": checkpoint_path })

    # 检查硬件设备
    check_utils(cfg, FLAGS)

    # 开始进行推理
    infer_process(cfg, FLAGS)
